// 防止头文件被多次包含
#pragma once

// 包含标准输入输出流库，用于屏幕输出
#include <iostream>
// 包含文件流库，用于文件输出
#include <fstream>
// 包含标准 C 语言输入输出库，用于 perror 等 (尽管这里主要用 std::cout)
#include <cstdio>
// 包含字符串库，用于 std::string
#include <string>
// 包含时间库，用于获取当前时间
#include <ctime>
// 包含变长参数处理库，用于处理可变参数列表
#include <cstdarg>
// 包含系统类型库 (getpid 需要 sys/types.h 和 unistd.h)
#include <sys/types.h>
// 包含 POSIX 标准库，用于 getpid, sleep 等
#include <unistd.h>
// 包含 POSIX 线程库，用于 pthread_mutex_t 等 (虽然这里直接用了 C 风格互斥量)
#include <pthread.h>
// 包含自定义的锁卫士头文件，用于 RAII 风格的互斥量管理
// 这个头文件应该定义了 LockGuard 类，用于封装 pthread_mutex_lock 和 pthread_mutex_unlock
#include "LockGuard.hpp"

// 全局变量，控制日志是否保存到文件 (true 表示保存到文件，false 表示输出到屏幕)
bool gIsSave = false;
// 常量字符串，指定日志文件名
const std::string logname = "log.txt";

// 1. 日志是有等级的
// 定义日志级别枚举，从低到高依次为：调试、信息、警告、错误、致命错误
enum Level
{
    DEBUG = 0, // 调试信息，用于开发调试，最详细的级别
    INFO,      // 一般信息，用于记录程序正常运行状态和关键事件
    WARNING,   // 警告信息，表示可能存在的问题，但不影响程序继续运行
    ERROR,     // 错误信息，表示程序运行出错，可能影响部分功能
    FATAL      // 致命错误，表示程序遇到严重问题，无法继续运行，可能需要终止程序
};

// 将日志消息保存到指定文件
// 这是一个辅助函数，负责实际的文件写入操作
// 参数 filename: 要写入的日志文件名
// 参数 message: 要写入文件的日志消息字符串
void SaveFile(const std::string &filename, const std::string &message)
{
    // 创建或打开文件流，以追加模式 (std::ios::app) 打开文件
    // 如果文件不存在则创建，如果存在则在文件末尾追加内容
    std::ofstream out(filename, std::ios::app);
    // 检查文件是否成功打开
    if (!out.is_open())
    {
        // 文件打开失败，可能由于权限不足或其他原因，简单返回，不进行写入
        perror("Failed to open log file"); // 输出错误信息到标准错误
        return;
    }
    // 将格式化好的日志消息写入文件
    out << message;
    // 关闭文件流
    out.close();
}

// 将日志级别枚举值转换为对应的字符串表示
// 参数 level: 日志级别枚举值 (int 类型)
// 返回值: 对应日志级别的字符串 (如 "Debug", "Info" 等)
// std::string LevelToString(int level)
// {
//     switch (level)
//     {
//     case DEBUG:
//         return "Debug";
//     case INFO:
//         return "Info";
//     case WARNING:
//         return "Warning";
//     case ERROR:
//         return "Error";
//     case FATAL:
//         return "Fatal";
//     default:
//         // 如果传入未知级别，返回 "Unknown"
//         return "Unknown";
//     }
// }

std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "Debug";
    case INFO:
        return "Info";
    case WARNING:
        return "Warning";
    case ERROR:
        return "Error";
    case FATAL:
        return "Fatal";
    default:
        return "Unknown"; // 添加默认返回值，避免警告
    }
}

// 获取当前时间的字符串表示
// 返回值: 格式为 "年-月-日 时:分:秒" 的时间字符串
std::string GetTimeString()
{
    // 获取当前时间，time_t 是一个表示时间的类型
    time_t curr_time = time(nullptr);
    // 将时间转换为本地时间结构体 struct tm
    struct tm *format_time = localtime(&curr_time);
    // 检查转换是否成功
    if (format_time == nullptr)
        // 转换失败，返回 "None"
        return "None";

    // 定义一个缓冲区用于存储格式化后的时间字符串
    char time_buffer[1024];
    // 使用 snprintf 将时间信息格式化到缓冲区
    // snprintf 比 sprintf 更安全，可以指定缓冲区大小，防止溢出
    snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900, // tm_year 是从 1900 年开始计算的年数
             format_time->tm_mon + 1,     // tm_mon 是从 0 开始计算的月份
             format_time->tm_mday,        // tm_mday 是月份中的天数
             format_time->tm_hour,        // 小时 (0-23)
             format_time->tm_min,         // 分钟 (0-59)
             format_time->tm_sec);        // 秒 (0-60)
    // 将格式化后的时间字符串从 char 数组转换为 std::string
    return time_buffer;
}

// 全局 POSIX 互斥锁，用于保护对日志输出资源的并发访问
// 在多线程环境中，多个线程可能同时调用 LogMessage 函数，
// 访问 std::cout 或文件流，这需要互斥量来保证线程安全。
// PTHREAD_MUTEX_INITIALIZER 是用于静态初始化的宏
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

// 2. 日志是有格式的
// 日志等级 时间 进程ID 文件名/行数 日志的内容
// 核心日志函数，负责格式化日志信息、处理可变参数、并进行输出（屏幕或文件）
// 参数 filename: 调用 LOG 宏时__FILE__捕获到的源文件名字符串
// 参数 line: 调用 LOG 宏时__LINE__捕获到的行号
// 参数 issave: 布尔值，指示是否将日志保存到文件 (由 LOG 宏传递 gIsSave 的值)
// 参数 level: 日志级别枚举值
// 参数 format: C 风格的格式化字符串，类似于 printf 的 format 参数
// 参数 ...: 可变参数列表，对应 format 字符串中的格式占位符
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{
    // 将日志级别转换为字符串
    std::string levelstr = LevelToString(level);
    // 获取当前时间字符串
    std::string timestr = GetTimeString();
    // 获取当前进程 ID (注意：在线程中，pid 是进程ID，tid 是线程ID。这里获取的是进程ID)
    // 在 Linux 下，gettid() 返回线程ID，但 getpid() 返回进程ID。
    // 这里的实现记录的是进程ID。如果需要记录线程ID，应该使用 gettid() 或 pthread_self() 获取 tid 的某种形式。
    pid_t selfid = getpid();

    // 定义一个缓冲区用于存储格式化后的用户日志内容
    char buffer[1024];
    // 用于存储可变参数的列表
    va_list arg;
    // 初始化可变参数列表，arg 指向 format 之后的第一个可变参数
    va_start(arg, format);
    // 使用 vsnprintf 将可变参数按照 format 格式化到 buffer
    // vsnprintf 是 printf 的变种，支持 va_list 参数和指定缓冲区大小
    vsnprintf(buffer, sizeof(buffer), format, arg);
    // 结束可变参数的处理
    va_end(arg); // arg = NULL

    // 格式化完整的日志消息字符串
    // 格式: [时间][级别][进程ID][文件名][行号] 具体消息
    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +
                          "[" + std::to_string(selfid) + "]" +
                          "[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;

    // --- 关键的同步部分：保护日志输出 ---
    // 使用自定义的 LockGuard 对象来管理全局互斥锁 lock
    // LockGuard 的构造函数会自动调用 pthread_mutex_lock(&lock) 获取锁
    // 这段代码 (从这里到 LockGuard 对象超出作用域) 就是临界区
    LockGuard lockguard(&lock);

    // 原始代码中有一个注释掉的 manual lock 调用，这可能是调试残留或错误
    // pthread_mutex_lock(&lock); // bug?? 这里的锁已经被 LockGuard 获取，再次手动获取会阻塞甚至死锁

    // 根据 issave (由 gIsSave 传递进来) 的值决定输出到屏幕还是文件
    if (!issave)
    {
        // 如果 issave 为 false (gIsSave 为 false)，输出到标准输出 (屏幕)
        std::cout << message; // std::cout 是共享资源，需要 mutex 保护
    }
    else
    {
        // 如果 issave 为 true (gIsSave 为 true)，调用 SaveFile 保存到文件
        SaveFile(logname, message); // 文件操作也需要 mutex 保护
    }

    // 原始代码中在条件分支后有一个注释掉的 manual unlock 或 lock 调用，这是错误的
    // pthread_mutex_lock(&lock); // bug?? 锁在这里由 LockGuard 管理，不应该手动操作

    // LockGuard 对象 lockguard 在此处超出作用域，其析构函数会自动调用 pthread_mutex_unlock(&lock)，释放锁
    // --- 临界区结束 ---

    // 原始代码中有一个注释掉的 std::cout 格式化示例，可以删除或保留
    // std::cout << levelstr << " : " << timestr << " : " << filename << " : " << line << ":" << buffer << std::endl;
}

// C99 新特性 __VA_ARGS__
// 日志宏定义，用于简化日志函数的调用
// LOG(level, format, ...) 这样的调用在预处理阶段会被替换为 LogMessage 函数的调用
// 宏的参数 level 对应 LogMessage 的 level 参数
// 宏的参数 format 对应 LogMessage 的 format 参数
// __VA_ARGS__ 表示宏的可变参数部分
// ##__VA_ARGS__ 是 GCC 扩展，处理可变参数为空的情况

// 宏定义中的反斜杠 \：行连接符
// 作用： 在 C/C++ 的预处理阶段，反斜杠 \ 紧跟在行末尾（后面不能有空格，只能有换行符）时，它充当一个行连接符

// #define LOG(level, format, ...)                                                \
//     do                                                                         \
//     {                                                                          \
//         /* LogMessage 函数的参数： */                                          \
//         /* __FILE__: 编译器预定义宏，表示当前源文件名 */                       \
//         /* __LINE__: 编译器预定义宏，表示当前行号 */                           \
//         /* gIsSave: 全局变量，控制是否保存到文件 */                            \
//         /* level: 宏的第一个参数，日志级别 */                                  \
//         /* format: 宏的第二个参数，格式化字符串 */                             \
//         /* ##__VA_ARGS__: 宏的可变参数，传递给 vsnprintf */                    \
//         LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \
//     } while (0) // 使用 do...while(0) 结构确保宏像一个单条语句使用，避免 if/else 引起的语法问题

#define LOG(level, format, ...)                                                \
    do                                                                         \
    {                                                                          \
        LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \
    } while (0)

// // 宏定义，用于启用文件日志输出
// #define EnableFile()    \
//     do                  \
//     {                   \
//         gIsSave = true; \
//     } while (0) // 将全局标志 gIsSave 设置为 true

// // 宏定义，用于启用屏幕日志输出 (默认行为)
// #define EnableScreen()   \
//     do                   \
//     {                    \
//         gIsSave = false; \
//     } while (0) // 将全局标志 gIsSave 设置为 false

#define EnableFile()    \
    do                  \
    {                   \
        gIsSave = true; \
    } while (0)

#define EnableScreen()   \
    do                   \
    {                    \
        gIsSave = false; \
    } while (0)

// 原始代码中有一个注释掉的测试可变参数的函数示例
// void Test(int num, ...)
// {
//     va_list arg;
//     va_start(arg, num);

//     while(num)
//     {
//         int data = va_arg(arg, int);
//         std::cout << "data: " << data << std::endl;
//         num--;
//     }

//     va_end(arg); // arg = NULL
// }